﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.XPath;
using Authorit.API;
using AuthoritExtensibility;

// The Authorit.API.Utilities namespace contains classes that help Author-it plug-in developers
// with common processes such as getting library configuration information, application context,
// and word counts.
[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
namespace Authorit.API.Utilities
{
	/// <summary>
	/// Stores a collection of instances of the <see cref="Authorit.API.Dto.ReleaseState"/> class.
	/// </summary>
	public class ReleaseStatesCollection : KeyedCollection<int, Dto.ReleaseState>
	{
		#region Constructors (1) 

		/// <summary>
		/// Initializes a new instance of the <see cref="ReleaseStatesCollection"/> class.
		/// </summary>
		public ReleaseStatesCollection() : base() { }

		#endregion Constructors 

		#region Methods (1) 

		// Protected Methods (1) 

		/// <summary>
		/// Gets the key for the specified instance of the <see cref="Authorit.API.Dto.ReleaseState"/> class.
		/// </summary>
		/// <param name="releaseState">The instance of the <see cref="Authorit.API.Dto.ReleaseState"/> class for which to find the key</param>
		/// <returns>Value of the <see cref="Authorit.API.Dto.ReleaseState.ReleaseStateId"/> property for <paramref name="releaseState" /></returns>
		protected override int GetKeyForItem(Dto.ReleaseState releaseState)
		{
			return releaseState.ReleaseStateId;
		}

		#endregion Methods 
	}

	#region PluginUtility
	/// <summary>
	/// Provides methods that return information about an Author-it library.
	/// </summary>
	public static class PluginUtility
	{
		#region Fields (4) 

		/// <summary>
		/// Stores a compiled regular expression used for
		/// removing HTML tags from text.
		/// </summary>
		/// <seealso cref="GetTopicText(int, Authorit.API.Services.Topic)"/>
		private static Regex htmlRegex = new Regex("<.*?>", RegexOptions.Compiled);

		#endregion Fields 

		#region Methods (15) 

		// Public Methods (14) 

		/// <summary>
		/// Gets an instance of the <see cref="Authorit.API.ApplicationContext"/> class.
		/// </summary>
		/// <param name="args">The instance of the <see cref="AuthoritExtensibility.AuthoritEventArgs"/> class to use for the context</param>
		/// <returns></returns>
		public static Authorit.API.ApplicationContext GetAppContext(AuthoritEventArgs args)
		{
			Authorit.API.ApplicationContext appContext = new ApplicationContext(GetLibraryConfigurationList(args));
			return appContext;
		}

		/// <summary>
		/// Gets a Jet library configuration.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class.</param>
		/// <returns>An instance of the <see cref="Authorit.API.Configuration.JetLibraryConfiguration"/> class for the Jet library specified by the <paramref name="args"/> parameter.</returns>
		/// <exception cref="ArgumentException">Thrown when the <see cref="AuthoritEventArgs.LibraryPath"/> property of the <paramref name="args"/> parameter is <value>null</value></exception>
		/// <exception cref="FileNotFoundException">Thrown when the jet library specified in the <see cref="AuthoritEventArgs.LibraryPath"/> property of the <paramref name="args"/> parameter does not exist</exception>
		/// <seealso cref="GetLibraryConfigurationList(AuthoritEventArgs)"/>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		/// <remarks>Use the <see cref="GetJetLibraryConfiguration(AuthoritEventArgs)"/> method when you know the library is a Jet library.</remarks>
		public static API.Configuration.JetLibraryConfiguration GetJetLibraryConfiguration(AuthoritEventArgs args)
		{

			if (null == args.LibraryPath)
			{
				throw new ArgumentException("args", "LibraryPath property cannot be null");
			}
			Configuration.JetLibraryConfiguration jetConfig = new Authorit.API.Configuration.JetLibraryConfiguration();

			System.IO.FileInfo jetFileInfo = new FileInfo(args.LibraryPath);
			if (jetFileInfo.Exists)
			{
				jetConfig.Alias = jetFileInfo.Name;
				jetConfig.Path = jetFileInfo.FullName;
			}
			else
			{
				string notFoundMessage = String.Format("Jet library {0} not found in path {1}", jetFileInfo.Name, jetFileInfo.DirectoryName);
				throw new FileNotFoundException(notFoundMessage, jetFileInfo.FullName);
			}
			return jetConfig;
		}

		/// <summary>
		/// Gets a new library configuration that contains information for the specified library connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains library connection information</param>
		/// <returns>A new instance of the <a href="http://devhub.author-it.com/Library/#b4293t2522n8130" target="_blank"><see cref="API.Configuration.LibraryConfigurationBase"/></a> class containing library information specified by the <paramref name="args"/></returns>
		/// <remarks>The <see cref="GetLibraryConfiguration(AuthoritEventArgs)"/> method returns <value>null</value> if the method cannot determine the library type.</remarks>
		public static API.Configuration.LibraryConfigurationBase GetLibraryConfiguration(AuthoritEventArgs args)
		{
			API.Configuration.LibraryConfigurationBase libConfig;
			libConfig = null;
			switch (GetLibraryType(args))
			{
				case LibraryType.Unknown:
					break;
				case LibraryType.Jet:
					libConfig = GetJetLibraryConfiguration(args);
					break;
				case LibraryType.Sql:
					libConfig = GetSqlLibraryConfiguration(args);
					break;
				default:
					break;
			}
			return libConfig;
		}

		/// <summary>
		/// Gets a new library configuration list that contains information for the specified library connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains library connection information</param>
		/// <returns>A new instance of the <a href="http://devhub.author-it.com/Library/#b4293t2524n8138" target="_blank"><see cref="API.Configuration.LibraryConfigurationList"/></a> class containing library information specified by the <paramref name="args"/> parameter</returns>
		/// <remarks>Use the <see cref="GetLibraryConfigurationList(AuthoritEventArgs)"/> method to get a new instance of the <see cref="API.Configuration.LibraryConfigurationList"/> class with one member.
		/// To add members to an existing instance of <see cref="API.Configuration.LibraryConfigurationList"/> use the <see cref="GetLibraryConfiguration(AuthoritEventArgs)"/> method and add the return value.</remarks>
		public static API.Configuration.LibraryConfigurationList GetLibraryConfigurationList(AuthoritEventArgs args)
		{
			API.Configuration.LibraryConfigurationList libConfigList = new Authorit.API.Configuration.LibraryConfigurationList();
			switch (GetLibraryType(args))
			{
				case LibraryType.Unknown:
					break;
				case LibraryType.Jet:
					libConfigList.Add(GetJetLibraryConfiguration(args));
					break;
				case LibraryType.Sql:
					libConfigList.Add(GetSqlLibraryConfiguration(args));
					break;
				default:
					break;
			}
			return libConfigList;
		}

		/// <summary>
		/// Gets the name of the library database.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		public static string GetDatabaseName(AuthoritEventArgs args)
		{
			string dbName = String.Empty;
			switch (GetLibraryType(args))
			{
				case LibraryType.Unknown:
					break;
				case LibraryType.Jet:
					dbName = args.LibraryPath;
					break;
				case LibraryType.Sql:
					dbName = args.SqlDatabase;
					break;
				default:
					break;
			}
			return dbName;
		}

		/// <summary>
		/// Gets the type of Author-it library specified by the <paramref name="args"/> parameter.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains Author-it library information</param>
		/// <returns>One of the valid <see cref="LibraryType"/> values</returns>
		/// <remarks>
		/// The method returns <see cref="LibraryType.Unknown"/> when the <paramref name="args"/> properties contain
		/// values for both <see cref="LibraryType.Jet"/> and <see cref="LibraryType.Sql"/> types
		/// or if the parameter contains null values for both types.
		/// </remarks>
		/// <seealso cref="GetLibraryConfigurationList(AuthoritEventArgs)"/>
		public static LibraryType GetLibraryType(AuthoritEventArgs args)
		{
			if (null == args.LibraryPath && (null == args.SqlServername || null == args.SqlDatabase))
			{
				return LibraryType.Unknown;
			}
			else if (null != args.LibraryPath && (null != args.SqlServername || null != args.SqlServername))
			{
				return LibraryType.Unknown;
			}
			else if (null != args.LibraryPath)
			{
				return LibraryType.Jet;
			}
			else if (null != args.SqlServername && null != args.SqlDatabase)
			{
				return LibraryType.Sql;
			}
			return LibraryType.Unknown;
		}

		/// <summary>
		/// The GetObjectXml(int, AuthoritEventArgs) method gets the Author-it XML for the specified object.
		/// </summary>
		/// <param name="objectId">The ID of the object for which to get XML</param>
		/// <param name="args">Instance of the <see cref="AuthoritExtensibility.AuthoritEventArgs"/> class that specifies the context
		/// to use when connecting to the library</param>
		/// <returns>A string containing Author-it XML for the specified object</returns>
		public static string GetObjectXml(int objectId, AuthoritEventArgs args)
		{
			string objectXml = String.Empty;
			Authorit.API.ApplicationContext appContext = GetAppContext(args);
			Authorit.API.Services.Authentication authService = appContext.GetServiceInstance<Authorit.API.Services.Authentication>();
			Authorit.API.Services.ObjectCommon objectCommon = appContext.GetServiceInstance<Authorit.API.Services.ObjectCommon>();
			Authorit.API.Dto.LoginResponse loginResponse = new Dto.LoginResponse();
			if (!authService.IsLoggedIn())
			{
				loginResponse = authService.Login(appContext.LibrariesConfiguration[0].Alias, args.Username, args.Password);
				switch (loginResponse.Result)
				{
					case Authorit.API.Dto.LoginResult.FailureDatabaseNotFound:
						break;
					case Authorit.API.Dto.LoginResult.FailureError:
						break;
					case Authorit.API.Dto.LoginResult.FailureIncorrectUsernameOrPassword:
						break;
					case Authorit.API.Dto.LoginResult.FailureLibraryFutureVersion:
						break;
					case Authorit.API.Dto.LoginResult.FailureLibraryOpen:
						break;
					case Authorit.API.Dto.LoginResult.FailureLibraryUpgradeNeeded:
						break;
					case Authorit.API.Dto.LoginResult.FailureLicensing:
						break;
					case Authorit.API.Dto.LoginResult.Success:
						objectXml = objectCommon.GetXml(objectId);
						break;
					default:
						break;
				}
			}
			else
			{
				objectXml = objectCommon.GetXml(objectId);
			}
			return objectXml;
		}

		/// <summary>
		/// Gets a SQL Server library configuration using the specified arguments.
		/// </summary>
		/// <param name="args">The arguments for the event</param>
		/// <returns>A SQL Server library configuration</returns>
		/// <remarks>Use the <see cref="GetJetLibraryConfiguration(AuthoritEventArgs)"/> method when you know the library specified by <paramref name="args"/> is a <see cref="LibraryType.Sql"/> library. Use the <see cref="GetLibraryType(AuthoritEventArgs)"/> method to determine the library type.</remarks>
		public static API.Configuration.SqlServerLibraryConfiguration GetSqlLibraryConfiguration(AuthoritEventArgs args)
		{
			ValidateParameter(args, "args");
			Configuration.SqlServerLibraryConfiguration sqlConfig = new Authorit.API.Configuration.SqlServerLibraryConfiguration();
			if (null == args.SqlDatabase || null == args.SqlServername)
			{
				throw new ArgumentException("SqlDatabase and SqlServername cannot be null", "args");
			}

			sqlConfig.Database = args.SqlDatabase;
			sqlConfig.Server = args.SqlServername;
			sqlConfig.Alias = String.Format("{0} on {1}", args.SqlDatabase, args.SqlServername);

			if (!IsTrustedSqlConnection(args))
			{
				sqlConfig.TrustedConnection = false;
				sqlConfig.LoginID = args.SqlUsername;
				sqlConfig.Password = args.SqlPassword;
			}
			else
			{
				sqlConfig.TrustedConnection = true;
			}

			return sqlConfig;
		}

		/// <summary>
		/// Gets the content of the specified text with the markup removed.
		/// </summary>
		/// <param name="topicTextXml">String containing the topic text XML</param>
		/// <returns>String containing the topic text with XML/HTML markup removed</returns>
		public static string GetTopicText(string topicTextXml)
		{
			return htmlRegex.Replace(topicTextXml, " ");
		}

		/// <summary>
		/// Gets the text of the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>A string containing the text of the specified topic with HTML tags removed</returns>
		public static string GetTopicText(int topicId, API.Services.Topic topicService)
		{
			return htmlRegex.Replace(GetTopicTextHtml(topicId, topicService), " ");
		}

		/// <summary>
		/// Gets the HTML text of the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>A string containing the HTML text of the specified topic</returns>
		public static string GetTopicTextHtml(int topicId, API.Services.Topic topicService)
		{
			string topicText = "";
			string xml = topicService.GetTopicTextXml(topicId);
			if (!String.IsNullOrEmpty(xml))
			{
				topicText = xml;
			}
			return topicText;
		}

		/// <summary>
		/// Gets the count of words in the specified string.
		/// </summary>
		/// <param name="text">The string containing words to count</param>
		/// <returns>The number of words in the specified string</returns>
		public static int GetWordCount(string text)
		{
			MatchCollection collection = Regex.Matches(text, @"[\S]+");
			return collection.Count;
		}

		/// <summary>
		/// Gets the count of words in the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>The number of words</returns>
		public static int GetWordCount(int topicId, API.Services.Topic topicService)
		{
			return GetWordCount(GetTopicText(topicId, topicService));
		}

		/// <summary>
		/// Indicates whether the specified SQL Server connection uses a trusted connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains SQL Server connection information</param>
		/// <returns>true if the information specifies a trusted connection; otherwise false</returns>
		public static bool IsTrustedSqlConnection(AuthoritEventArgs args)
		{
			ValidateParameter(args, "args");
			bool trusted = false;

			if (null != args.SqlServername && null != args.SqlDatabase && null == args.SqlUsername)
			{
				trusted = true;
			}

			return trusted;
		}

		// Private Methods (1) 

		/// <summary>
		/// Validates whether an object is not null.
		/// </summary>
		/// <param name="param">The parameter to validate</param>
		/// <param name="paramName">The name of the parameter</param>
		/// <remarks>The <see cref="PluginUtility"/> class uses the <see cref="ValidateParameter(object, string)"/> method
		/// to validate paramters passed to public methods.</remarks>
		private static void ValidateParameter(object param, string paramName)
		{
			if (null == param)
			{
				throw new ArgumentNullException(paramName);
			}
		}

		#endregion Methods 
	}
	#endregion

	#region LibraryType
	/// <summary>
	/// Specifies the type of Author-it library.
	/// </summary>
	/// <remarks>The <see cref="PluginUtility.GetLibraryConfiguration(AuthoritEventArgs)"/>, <see cref="PluginUtility.GetLibraryConfigurationList(AuthoritEventArgs)"/>, and <see cref="PluginUtility.GetLibraryType(AuthoritEventArgs)"/> methods use the <see cref="LibraryType"/> enumeration when determing the type of library.</remarks>
	/// <seealso cref="Authorit.API.ApplicationContext"/>
	public enum LibraryType
	{
		#region Enumeration values
		#region Unknown = 0
		/// <summary>
		/// Unknown or invalid library information
		/// </summary>
		Unknown = 0,
		#endregion
		#region Jet = 1
		/// <summary>
		/// Jet file library
		/// </summary>
		Jet = 1,
		#endregion
		#region Sql = 2
		/// <summary>
		/// SQL Server library
		/// </summary>
		Sql = 2
		#endregion
		#endregion
	}

	/// <summary>
	/// Specifies plug-in button image size
	/// </summary>
	public enum ButtonSize
	{
		/// <summary>
		/// Image is 16 pixels x 16 pixels
		/// </summary>
		Small = 16,
		/// <summary>
		/// Image is 32 pixels x 32 pixels
		/// </summary>
		Large = 32,
	}

	#endregion
}
